import { ServiceIdentifierOrFunc } from 'inversify/dts/annotation/inject';
import { Container, inject, interfaces, multiInject, optional } from 'inversify';
import { Metadata_Key } from '../../constants';
import { ContainerProvider } from '../../container';

/** Autowired装饰器配置 */
export interface AutowiredOption {
  /** 注入的注解ID */
  id?: ServiceIdentifierOrFunc,
  /** 是否必须注入，默认：true */
  required?: boolean;
  /**
   * 默认 false<br />
   * false: 调用inject/multiInject实现注入<br />
   * true: 每次取值从IOC容器里面取<br />
   */
  useGet?: boolean
}

export namespace AutowiredOption {
  export function is(options: any): options is AutowiredOption {
    return options && (options.id !== undefined || options.required !== undefined || options.detached !== undefined);
  }
}

/** Autowired装饰器定义 */
export interface AutowiredDecorator {
  /**
   * 参数装饰器工厂
   * @param option Autowired装饰器配置
   */
  (option?: ServiceIdentifierOrFunc | AutowiredOption): (target: Object, propertyKey: string | symbol, parameterIndex?: number) => void;
}

/** Autowired装饰器 */
const Autowired = <AutowiredDecorator>function (idOrOption?: ServiceIdentifierOrFunc | AutowiredOption): PropertyDecorator | ParameterDecorator {
  const option = getAutowiredOption(idOrOption);
  return (target: Object, propertyKey: string | symbol, parameterIndex?: number) => {
    applyAutowiredDecorator(option, target, <string>propertyKey, parameterIndex);
  };
};

/**
 * 获取Autowired装饰器配置
 * @param idOrOption 组件ID或者Autowired装饰器配置
 */
function getAutowiredOption(idOrOption?: ServiceIdentifierOrFunc | AutowiredOption): AutowiredOption {
  let option: AutowiredOption = {};
  if (AutowiredOption.is(idOrOption)) {
    option = {...idOrOption};
  } else if (idOrOption) {
    option = {id: idOrOption};
  }
  return option;
}

/**
 * 默认的注入逻辑
 * @param option          Autowired装饰器配置
 * @param isMulti         是否多值注入
 * @param target          注入目标对应Class(对于静态成员来说是类的构造函数，对于实例成员是类的原型对象)
 * @param propertyKey     属性的名字(属性注入)/方法的名字(参数注入)
 * @param parameterIndex  参数在函数参数列表中的索引(参数注入)
 */
const doInjectFuc = (option: AutowiredOption, isMulti: boolean, target: Object, propertyKey: string, parameterIndex?: number): void => {
  if (isMulti) {
    multiInject(<interfaces.ServiceIdentifier<any>>option.id)(target, propertyKey, parameterIndex);
  } else {
    inject(option.id!)(target, propertyKey, parameterIndex);
  }
  if (!option.required) {
    optional()(target, propertyKey, parameterIndex);
  }
}

// noinspection JSUnusedLocalSymbols
/**
 * 默认的获取注入的值
 * @param option      Autowired装饰器配置
 * @param isMulti     是否多值注入
 * @param container   IOC容器
 * @param target      注入目标对应Class(对于静态成员来说是类的构造函数，对于实例成员是类的原型对象)
 * @param propertyKey 属性的名字(属性注入)/方法的名字(参数注入)
 */
const doGetValueFuc = (option: AutowiredOption, isMulti: boolean, container: Container, target: Object, propertyKey: string): any => {
  let component: any;
  try {
    if (isMulti) {
      component = container.getAll(<interfaces.ServiceIdentifier<any>>option.id);
    } else {
      component = container.get(<interfaces.ServiceIdentifier<any>>option.id);
    }
  } catch (error) {
    if (!option.required) {
      return undefined;
    }
    throw error;
  }
  return component;
}

/**
 * 应用Autowired装饰器
 * @param option          Autowired装饰器配置
 * @param target          注入目标对应Class(对于静态成员来说是类的构造函数，对于实例成员是类的原型对象)
 * @param propertyKey     属性的名字(属性注入)/方法的名字(参数注入)
 * @param parameterIndex  参数在函数参数列表中的索引(参数注入)
 * @param doInject        可自定义的注入逻辑(useGet===false)
 * @param doGetValue      可自定义的获取注入的值(useGet===true)
 */
function applyAutowiredDecorator(option: AutowiredOption, target: Object, propertyKey: string, parameterIndex?: number, doInject = doInjectFuc, doGetValue = doGetValueFuc): void {
  let type: any;
  if (parameterIndex !== undefined) {
    // 参数装饰器
    type = Reflect.getMetadata(Metadata_Key.paramTypes, target, propertyKey)[parameterIndex];
  } else {
    // 属性装饰器
    type = Reflect.getMetadata(Metadata_Key.type, target, propertyKey);
  }
  // 是否是数组注入(多值注入)
  const isMlt = (type === Array);
  // Autowired装饰器配置默认值
  const defaultAutowiredOption: AutowiredOption = {
    id: type,
    required: true,
    useGet: false,
  };
  const opt = {...defaultAutowiredOption, ...option};
  if (opt.useGet) {
    // 从IOC容器里面取值
    if (parameterIndex !== undefined) {
      // ???
      throw new Error(`The ${target.constructor.name} itself is not injected into the container, so the parameter injection of the constructor is not supported.`);
    }
    createAutowiredProperty(opt, isMlt, doGetValue, target, propertyKey);
  } else {
    // 调用inject/multiInject实现注入
    doInject(opt, isMlt, target, propertyKey, parameterIndex);
  }
}

/**
 * 修改自动注入的属性的get访问器
 * @param option      Autowired装饰器配置
 * @param isMulti     是否多值注入
 * @param doGetValue  可自定义的获取注入的值(useGet===true)
 * @param target      注入目标对应Class(对于静态成员来说是类的构造函数，对于实例成员是类的原型对象)
 * @param propertyKey 属性的名字
 */
function createAutowiredProperty(option: AutowiredOption, isMulti: boolean, doGetValue: typeof doGetValueFuc, target: any, propertyKey: string): void {
  let value: any;
  Object.defineProperty(target, propertyKey, {
    enumerable: true,
    get(): any {
      if (value !== undefined) {
        return value;
      }
      const container = ContainerProvider.provide();
      value = doGetValue(option, isMulti, container, target, propertyKey);
      return value;
    }
  });
}

export {
  getAutowiredOption,
  applyAutowiredDecorator,
  createAutowiredProperty,
  Autowired,
}
